﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RaytracerLib.MathLib;
using System.Diagnostics;
using TomanuExtensions;
using YAXLib;

namespace RaytracerLib.UVMappers
{
    public class TorodidalUVMapper : UVMapper
    {
        private double m_local_small_radius;
        private double m_local_big_radius;
        private double m_big_radius;
        private double m_small_radius;

        [YAXNode]
        public double BigRadius
        {
            get
            {
                return m_big_radius;
            }
            set
            {
                m_big_radius = value;
                UpdateLocalRadiuses();
            }
        }

        [YAXNode]
        public double SmallRadius
        {
            get
            {
                return m_small_radius;
            }
            set
            {
                m_small_radius = value;
                UpdateLocalRadiuses();
            }
        }

        [YAXOnDeserialized]
        private void UpdateLocalRadiuses()
        {
            m_local_small_radius = SmallRadius / (SmallRadius + BigRadius);
            m_local_big_radius = BigRadius / (SmallRadius + BigRadius);
        }

        public TorodidalUVMapper(double a_big_radius = 0, double a_small_radius = 0)
        {
            BigRadius = a_big_radius;
            SmallRadius = a_small_radius;
        }

        public override Vector2 Map(Vector3 a_uvw)
        {
            Vector3 local_pos = (a_uvw - Vector3.HALF) / new Vector3(0.5, 0.5 / m_local_small_radius, 0.5);
            Vector3 p1 = new Vector3(local_pos.X, 0, local_pos.Z).Normalized * m_local_big_radius;
            double small_radius = (local_pos - p1).Length;

            double ca = local_pos.Y / small_radius;

            Debug.Assert(ca.IsAlmostRelativeGreaterOrEqualThen(-1));
            Debug.Assert(ca.IsAlmostRelativeLessOrEqualThen(1));
            ca = DoubleExtensions.Limit(ca, -1, 1);

            double v = Math.Acos(ca) / MathExtensions.PI / 2;

            if (local_pos.X * local_pos.X + local_pos.Z * local_pos.Z > m_local_big_radius * m_local_big_radius)
                v = v + 0.25;
            else
            {
                if (local_pos.Y > 0)
                    v = 0.25 - v;
                else
                    v = 1.25 - v;
            }

            double u = Math.Atan2(local_pos.X, local_pos.Z) / (2 * MathExtensions.PI);

            if (local_pos.X < 0)
                u = u + 1;

            return new Vector2(u, v);
        }

        public override UVMappingMethod Method
        {
            get
            {
                return UVMappingMethod.Toroidal;
            }
        }

        public override string ToString()
        {
            return String.Format("Toroidal uv mapper: R: {0}, r: {1}", BigRadius, SmallRadius);
        }
    }
}
